package com.ms.common.util;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ms.common.function.ReturnableClosure;

@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class StringUtils {

    public static final String ENCODING = "UTF-8";
    public static final String EMPTY = "";

    private static class ToStringer<T> implements ReturnableClosure<T, String> {
        @Override
        public String execute(T object) {
            return ObjectUtils.toString(object);
        }
    }


    /***
     * 清除不可见unicode
     * http://www.regular-expressions.info/unicode.html
     * https://stackoverflow.com/questions/6198986/how-can-i-replace-non-printable-unicode-characters-in-java
     * @param source
     * @return
     */
    public static String cleanInvisibleUnicode(String source){
        if(source.length()<1){
            return source;
        }
        return source.replaceAll("\\p{C}", "");
    }
    public static boolean isNullOrBlankString(Object value){
        return value==null || (String.class.isInstance(value) && "".equals(value.toString()));
    }

    public static String emptyIfNull(Object str){
        return str==null?EMPTY:str.toString();
    }

    public static String lowerCase(String str) {
        if (str == null) {
            return EMPTY;
        }
        return str.toLowerCase();
    }

    public static String encode(String str) {
        if (StringUtils.isBlank(str)) {
            return "";
        }
        try {
            return URLEncoder.encode(str, ENCODING);
        } catch (Exception e) {
            return str;
        }
    }

    public static String decode(String str) {
        try {
            return URLDecoder.decode(str, ENCODING);
        } catch (UnsupportedEncodingException e) {
            return str;
        }
    }


    public static boolean objectNull(Object obj) {
        return obj == null || "".equals(obj.toString().trim());
    }

    public static boolean isBlank(String str) {
        return str == null || "".equals(str.trim());
    }

    public static boolean isNotBlank(String str) {
        return !isBlank(str);
    }
    public static boolean isNotNull(Object str) {
        return !objectNull(str);
    }

    public static boolean hasLength(CharSequence str) {
        return (str != null && str.length() > 0);
    }

    public static boolean hasLength(String str) {
        return hasLength((CharSequence) str);
    }

    public static String bar2UnderLine(String str) {
        return str.replace('-', '_');
    }

    public static String getLastName(String clsName, String sep) {
        int index = clsName.lastIndexOf(sep);
        if (index != -1) {
            return clsName.substring(index);
        } else {
            return clsName;
        }
    }
    public static String getFirstWord(String str) {
        if(StringUtils.isBlank(str)) {
            return LangUtil.EMPTY_STRING;
        }

        char[] chars = str.toCharArray();
        StringBuilder first = new StringBuilder();
        first.append(chars[0]);
        for (int i = 1; i < chars.length; i++) {
            if(Character.isUpperCase(chars[i])){
                break;
            }else{
                first.append(chars[i]);
            }
        }
        return first.toString();
    }

    public static boolean hasText(CharSequence str) {
        if (!hasLength(str)) {
            return false;
        }
        int strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(str.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    public static String append(String... strings) {
        if (strings == null || strings.length == 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (String str : strings) {
            sb.append(str);
        }
        return sb.toString();
    }

    public static boolean equals(String str1, String str2) {
        return str1 == null ? str2 == null : str1.equals(str2);
    }

    /**
     * 首字母 大写
     * @param str
     * @return
     */
    public static String capitalize(String str) {
        int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return str;
        }
        return new StringBuffer(strLen).append(Character.toTitleCase(str.charAt(0))).append(str.substring(1)).toString();
    }

    /**
     * 首字母 小写
     * @param str
     * @return
     */
    public static String uncapitalize(String str) {
        int strLen;
        if (str == null || (strLen = str.length()) == 0) {
            return str;
        }
        return new StringBuffer(strLen).append(Character.toLowerCase(str.charAt(0))).append(str.substring(1)).toString();
    }

    /**
     * java 属性名转换成由 _ 分割驼峰属性名数据库属性名
     * @param str
     * @return
     */
    public static String propertyNameToColumnName(String str) {
        return convert2UnderLineName(str);
    }


    /**
     * 按照分隔符 op 把字符串 str转成按单词排列的首字母大写，比如 property-name 转成 propertyName
     * @param str
     * @return
     */
    public static String toPropertyName(String str) {
        return toCamel(str, false);
    }


    public static String toClassName(String str) {
        return toCamel(str, true);
    }

    public static String toCamel(String str, boolean isFirstUpper) {
        return toCamel(str, '_', isFirstUpper);
    }
    /**
     * 按照分隔符 op 把字符串 str转成按单词排列的首字母大写，比如 property-name 转成 propertyName
     * @param str the property
     * @param op 分隔字符
     * @param isFirstUpper 是否让第一个 字符转成大写字母
     * @return 转换后的字符串
     */
    public static String toCamel(String str, char op, boolean isFirstUpper) {
        //1、原样返回
        if (str==null || str.length()==0) {
            return str;
        }
        if (str.indexOf(op) == -1) {
            //2、没有分割符号
            str = str.toLowerCase();
            if (isFirstUpper && Character.isLowerCase(str.charAt(0))) {
                //3、把第一个字符转成大写 并返回
                return str.substring(0, 1).toUpperCase() + str.substring(1);
            } else {
                return str;
            }
        }
        //4、有分割符号
        char[] chars = str.toCharArray();
        //线程安全
        StringBuilder newStr = new StringBuilder();
        boolean needUpper = isFirstUpper;
        for (int i = 0; i < chars.length; i++) {
            char c = Character.toLowerCase(chars[i]);
            //5、第一个是否需要转成大写
            if (needUpper) {
                c = Character.toUpperCase(c);
                needUpper = false;
            }
            //6、遇到分隔符，下载下一个字母需要转成大写，并跳过该字符，进如下一循环
            if (c == op) {
                needUpper = true;
                continue;
            }
            //7、把当前字符放到新的字符
            newStr.append(c);
        }
        return newStr.toString();
    }


    /***
     * 不把本身大写转为小写
     * @author weishao zeng
     * @param str
     * @param op
     * @param isFirstUpper 是否把第一个字符转为大写
     * @return
     */
    public static String toCamelWithoutConvert2LowerCase(String str, char op, boolean isFirstUpper) {
        if (str==null || str.length()==0) {
            return str;
        }
        if (str.indexOf(op) == -1) {
            if (isFirstUpper && Character.isLowerCase(str.charAt(0))) {
                return str.substring(0, 1).toUpperCase() + str.substring(1);
            } else {
                return str;
            }
        }
        char[] chars = str.toCharArray();
        StringBuilder newStr = new StringBuilder();
        boolean needUpper = isFirstUpper;
        for (int i = 0; i < chars.length; i++) {
            char c = chars[i];
            if (needUpper) {
                c = Character.toUpperCase(c);
                needUpper = false;
            }
            if (c == op) {
                needUpper = true;
                continue;
            }
            newStr.append(c);
        }
        return newStr.toString();
    }

    public static boolean hasUpper(String str) {
        char[] chars = str.toCharArray();
        for (char ch : chars) {
            if (Character.isUpperCase(ch)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 驼峰属性名 转换为下划线分隔的名称
     * @param name
     * @return
     */
    public static String convert2UnderLineName(String name) {
        return convertWithSeparator(name, "_");
    }

    /**
     * 使用 指定分隔符 把驼峰属性名转换为由分隔符 op 分隔的 前后每个单词是小姐字母的属性名
     * @param name
     * @param op
     * @return
     */
    public static String convertWithSeparator(String name, String op) {
        if(name==null) {
            return "";
        }
        //1、创建 StringBuffer
        StringBuffer table = new StringBuffer();
        //2、字符串转换为数组
        char[] chars = name.toCharArray();
        //3、数组第一个字符转换为小写字符
        table.append(Character.toLowerCase(chars[0]));
        //4、从第一个字符开始遍历
        for (int i = 1; i < chars.length; i++) {
            char ch = chars[i];
            if (Character.isUpperCase(ch)) {
                //5、遇到大写字母，则先在其前面加入下划线 _ ,在把该大写字母转成小写字母
                table.append(op);
                ch = Character.toLowerCase(ch);
            }
            table.append(ch);
        }
        return table.toString();
    }

    public static String getSimpleBeanName(String clsName) {
        if (clsName.indexOf('.') == -1) {
            return clsName;
        }
        int index = clsName.lastIndexOf('.');
        String sn = clsName.substring(index + 1);
        return uncapitalize(sn);
    }

    public static String getClassShortName(Object obj) {
        Assert.notNull(obj);
        Class<?> cls = ReflectUtils.getObjectClass(obj);
        return uncapitalize(cls.getSimpleName());
    }

    /*
     * from apache common org.apache.commons.lang3.StringUtils
     *
     *
     */

    public static String[] split(String str, char separatorChar) {
        return splitWorker(str, separatorChar, false);
    }

    private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
        // Performance tuned for 2.0 (JDK1.4)

        if (str == null) {
            return null;
        }
        int len = str.length();
        if (len == 0) {
            return new String[0];
        }
        List list = new ArrayList();
        int i = 0, start = 0;
        boolean match = false;
        boolean lastMatch = false;
        while (i < len) {
            if (str.charAt(i) == separatorChar) {
                if (match || preserveAllTokens) {
                    list.add(str.substring(start, i));
                    match = false;
                    lastMatch = true;
                }
                start = ++i;
                continue;
            } else {
                lastMatch = false;
            }
            match = true;
            i++;
        }
        if (match || (preserveAllTokens && lastMatch)) {
            list.add(str.substring(start, i));
        }
        return (String[]) list.toArray(new String[list.size()]);
    }

    public static List<String> splitWithRetainSeparator(String str, String separatorChars, String retainSeparator) {
        if (separatorChars == null) {
            separatorChars = " ";
        }
        separatorChars += trimToEmpty(retainSeparator);
        int len = str.length();
        int wordIndex = 0;
        List<String> strlist = new ArrayList<String>();
        String w = null;
        char ch;
        for (int i = 0; i < len; i++) {
            ch = str.charAt(i);
            if (separatorChars.indexOf(ch) != -1) {
                w = str.substring(wordIndex, i);
                strlist.add(w);
                if (retainSeparator.indexOf(ch) != -1) {
                    strlist.add(String.valueOf(ch));
                }
                wordIndex = i + 1;
            }
        }
        if (wordIndex < len) {
            w = str.substring(wordIndex);
            strlist.add(w);
        }
        return strlist;
    }

    public static String[] split(String str, String separatorChars) {
        return str.split(separatorChars);
    }

    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }



    public static String substring(String str, int beginIndex, int endIndex) {
        return str.substring(beginIndex, endIndex);
    }

    public static String substringAfter(String str, String separator) {
        int index = str.indexOf(separator);
        if(index>-1 && str.length()-1 >index){
            return str.substring(index+1);
        }
        return null;
    }

    public static String substringBefore(String str, String separator) {
        int index = str.indexOf(separator);
        if(index>1){
            return str.substring(0,index);
        }
        return null;
    }



    public static String buildString(Object obj) {
        if (obj == null) {
            return "null";
        }
        StringBuilder toString = new StringBuilder("{");
        String str = null;
        if (Collection.class.isAssignableFrom(obj.getClass()) || obj.getClass().isArray()) {
            int index = 0;
            List list = asList(obj);
            if (list == null) {
                return "null";
            }
            for (Object o : (Collection) list) {
                str = buildString(o);
                if (StringUtils.isBlank(str)) {
                    continue;
                }
                if (index != 0) {
                    toString.append(", ");
                }
                toString.append(str);
                index++;
            }
        } else if (Map.class.isAssignableFrom(obj.getClass())) {
            int index = 0;
            for (Object o : ((Map) obj).entrySet()) {
                str = buildString(o);
                if (StringUtils.isBlank(str)) {
                    continue;
                }
                if (index != 0) {
                    toString.append(", ");
                }
                toString.append(str);
            }
        } else {
            str = obj.toString();
            return "[" + str + "]";
        }
        toString.append("}");
        return toString.toString();
    }

    public static List asList(Object array) {
        if (array == null) {
            return null;
        }
        List list = null;
        if (Collection.class.isAssignableFrom(array.getClass())) {
            if (List.class.isAssignableFrom(array.getClass())) {
                list = (List) array;
            } else {
                list = new ArrayList();
                list.addAll((Collection) array);
            }
        } else if (array.getClass().isArray()) {
            list = new ArrayList();
            for (Object o : (Object[]) array) {
                list.add(o);
            }
        } else {
            list = new ArrayList();
            list.add(array);
        }
        return (list == null || list.isEmpty()) ? null : list;
    }

    public static String join(String separator, Object... array) {
        if (array == null) {
            return EMPTY;
        }
        return join(array, separator, 0, array.length);
    }

    public static String join(Object[] array, String separator) {
        if (array == null) {
            return EMPTY;
        }
        return join(array, separator, 0, array.length);
    }

    public static <T> String join(T[] array, String separator, ReturnableClosure<T, String> it) {
        if (array == null) {
            return EMPTY;
        }
        return join(array, separator, 0, array.length, it);
    }

    public static <T> String join(Iterator<T> iterator, String separator) {
        return join(iterator, separator, new ToStringer<T>());
    }

    public static <T> String join(Iterator<T> iterator, String separator, ReturnableClosure<T, String> it) {
        if (it==null) {
            it = new ToStringer<T>();
        }
        // handle null, zero and one elements before building a buffer
        if (iterator == null) {
            return EMPTY;
        }
        if (!iterator.hasNext()) {
            return EMPTY;
        }
        T first = iterator.next();
        if (!iterator.hasNext()) {
            return it.execute(first);
        }

        // two or more elements
        StringBuilder buf = new StringBuilder(256); // Java default is 16,
        // probably too small
        if (first != null) {
            buf.append(it.execute(first));
        }
        while (iterator.hasNext()) {
            if (separator != null) {
                buf.append(separator);
            }
            T obj = iterator.next();
            if (obj != null) {
                if (it != null) {
                    buf.append(it.execute(obj));
                } else {
                    buf.append(obj);
                }
            }
        }
        return buf.toString();
    }

    public static String join(Collection<?> collection, String separator) {
        if (collection == null) {
            return EMPTY;
        }
        return join(collection.iterator(), separator);
    }

    public static <K, V> String join(Map<K, V> map, String separator) {
        return join(map, ":", separator);
    }

    public static <K, V> String join(Map<K, V> map, String entrySeparator, String separator) {
        return join(map.entrySet(), separator, entry -> {
            return entry.getKey().toString() + entrySeparator + entry.getValue().toString();
        });
    }

    public static <T> String join(Collection<T> collection, String separator, ReturnableClosure<T, String> it) {
        if (collection == null) {
            return EMPTY;
        }
        return join(collection.iterator(), separator, it);
    }

    public static <T> String join(T[] array, String separator, int startIndex, int endIndex) {
        return join(array, separator, startIndex, endIndex, null);
    }

    public static <T> String join(T[] array, String separator, int startIndex, int endIndex, ReturnableClosure<T, String> it) {
        if (array == null) {
            return EMPTY;
        }
        if (separator == null) {
            separator = EMPTY;
        }

        int bufSize = (endIndex - startIndex);
        if (bufSize <= 0) {
            return EMPTY;
        }

        bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + separator.length());

        StringBuffer buf = new StringBuffer(bufSize);

        for (int i = startIndex; i < endIndex; i++) {
            if (i > startIndex) {
                buf.append(separator);
            }
            if (array[i] != null) {
                if (it != null) {
                    buf.append(it.execute(array[i]));
                } else {
                    buf.append(array[i]);
                }
            }
        }
        return buf.toString();
    }

    public static String unicodeToString(String str) {
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(str);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            str = str.replace(matcher.group(1), ch + "");
        }
        return str;
    }

    public static String stringToUnicode(String str) {
        String result = "";
        for (int i = 0; i < str.length(); i++) {
            result += "\\u" + Integer.toHexString(str.charAt(i));
        }
        return result;
    }

    public static String trimToEmpty(Object str) {
        return str == null ? EMPTY : str.toString().trim();
    }

    public static String[] trim(String[] strs) {
        List<String> strList = new ArrayList<String>(strs.length);
        for (String str : strs) {
            if (str == null) {
                continue;
            }
            strList.add(str.trim());
        }
        return strList.toArray(new String[strList.size()]);
    }

    public static void addWithTrim(Collection collections, boolean ignoreBlank, String... strs) {
        for (String str : strs) {
            if (ignoreBlank) {
                if (isBlank(str)) {
                    continue;
                }
            } else {
                // defaut is ignore null
                if (str == null) {
                    continue;
                }
            }
            collections.add(str.trim());
        }
    }

    public static void addWithTrim(Collection collections, String str, String op) {
        String[] strs = split(str, op);
        addWithTrim(collections, false, strs);
    }

    public static String defaultValue(String val, String def){
        if(isBlank(val)) {
            return def;
        }
        return val;
    }

    public static String defaultValues(String val, String... defs){
        if(isBlank(val)){
            for(String def : defs){
                if(isNotBlank(def)) {
                    return def;
                }
            }
        }
        return val;
    }

    public static String firstNotBlank(Object... defs){
        if(LangUtil.isEmpty(defs)) {
            return LangUtil.EMPTY_STRING;
        }
        for(Object def : defs){
            if(def!=null && isNotBlank(def.toString())) {
                return def.toString();
            }
        }
        return LangUtil.EMPTY_STRING;
    }


    public static String trimStartWith(String path, String prefix) {
        return trimLeft(path, prefix);
    }

    public static String surroundWith(String path, String prefix) {
        if (path == null) {
            path = EMPTY;
        }
        return appendEndWith(appendStartWith(path, prefix), prefix);
    }



    public static String appendStartWith(String path, String prefix) {
        if (path == null) {
            path = EMPTY;
        }
        if (!path.startsWith(prefix)) {
            path = prefix + path;
        }
        return path;
    }

    public static String appendEndWith(String path, String postfix) {
        if (path == null) {
            path = EMPTY;
        }
        if (path.endsWith(postfix)) {
            return path;
        }
        return path + postfix;
    }
    public static String appendArroundWith(final String str, String appendString) {
        String newString = null;
        if (str == null) {
            newString = EMPTY;
        }
        newString = appendStartWith(str, appendString);
        newString = appendEndWith(newString, appendString);
        return newString;
    }
    public static String getSqlLikeString(final String str) {
        return StringUtils.appendArroundWith(str, "%");
    }

    public static String trimEndWith(String path, String postfix) {
        return trimRight(path, postfix);
    }

    public static String ellipsis(String source, int size, String ellipsisStr) {
        if (isBlank(source)) {
            return "";
        }
        if (source.length() > size) {
            return source.substring(0, size) + ellipsisStr;
        } else {
            return source;
        }
    }

    public static String getFirstNotBlank(String... strs) {
        if (LangUtil.isEmpty(strs)) {
            return EMPTY;
        }
        for (String str : strs) {
            if (isNotBlank(str)) {
                return str;
            }
        }
        return EMPTY;
    }



    public static String trimRight(String text, String trimstr) {
        if(isBlank(text) || isBlank(trimstr)) {
            return text;
        }
        String rs = text;
        while(rs.endsWith(trimstr)){
            rs = rs.substring(0, rs.length()-trimstr.length());
        }
        return rs;
    }
    public static String trimLeft(String text, String trimstr) {
        if(isBlank(text) || isBlank(trimstr)) {
            return text;
        }
        String rs = text;
        int index = 0;
        while(rs.startsWith(trimstr, index)){
            index += trimstr.length();
        }
        rs = index>0?rs.substring(index):rs;
        return rs;
    }

    public static String trim(String text, String trimstr) {
        if(isBlank(text) || isBlank(trimstr)) {
            return text;
        }
        return trimRight(trimLeft(text, trimstr), trimstr);
    }


    public static boolean isStringStartWithAnyOne(final String str, Collection<String> prefixList) {
        if (LangUtil.isEmpty(prefixList)) {
            return false;
        }
        return prefixList.stream().anyMatch(prefix -> str.startsWith(prefix));
    }

    /**
     * 判断数组 searchStrs中是存在 一个 str
     * @param str: 源字符序列，在其中进行搜索
     * @param searchStrs: 可变参数列表，表示要搜索的目标字符序列数组。
     * @return int 返回目标字符序列中第一个匹配项在源字符序列中的索引位置。如果未找到任何匹配项，则返回 -1。
     */
    public static int indexOfAny_old(CharSequence str, CharSequence... searchStrs) {
        if (str != null && searchStrs != null) {
            //1、搜索字符序列
            int sz = searchStrs.length;
            //2、
            int ret = Integer.MAX_VALUE;
            //3、
            for(int i = 0; i < sz; ++i) {
                CharSequence search = searchStrs[i];
                if (search != null) {
                    int tmp = charSequenceIndexOf(str, search, 0);
                    if (tmp != -1 && tmp < ret) {
                        ret = tmp;
                    }
                }
            }
            return ret == Integer.MAX_VALUE ? -1 : ret;
        } else {
            return -1;
        }
    }

    public static int indexOfAny(CharSequence str, CharSequence... searchStrs) {
        if (str == null || searchStrs == null) {
            return -1;
        }
        int minIndex = Integer.MAX_VALUE;
        for (CharSequence search : searchStrs) {
            if (search != null) {
                // 如果两个都是String类型，直接使用String的方法
                if (str instanceof String && search instanceof String) {
                    int index = ((String) str).indexOf((String) search);
                    if (index >= 0 && index < minIndex) {
                        minIndex = index;
                    }
                } else {
                    // 否则转换为String再查找
                    int index = charSequenceIndexOf(str, search, 0);
                    if (index >= 0 && index < minIndex) {
                        minIndex = index;
                    }
                }
            }
        }
        return minIndex == Integer.MAX_VALUE ? -1 : minIndex;
    }

    // 假设这是之前定义的 charSequenceIndexOf_bak 方法
    private static int charSequenceIndexOf_bak(CharSequence cs, CharSequence searchChar, int start) {
        if (cs == null || searchChar == null) {
            throw new NullPointerException("Neither cs nor searchChar can be null.");
        }
        if (cs instanceof String && searchChar instanceof String) {
            return ((String) cs).indexOf((String) searchChar, start);
        }
        return cs.toString().indexOf(searchChar.toString(), start);
    }

    /**
     * 查找 searchChar 在 cs 中的首次出现的位置，从 start开始查找
     * @param cs    源字符序列，在其中进行搜索
     * @param searchChar    要搜索的目标字符序列
     * @param start     开始查找的位置索引。查找将从这个位置开始，直到字符序列的末尾。
     * @return
     */
    public static int charSequenceIndexOf(CharSequence cs, CharSequence searchChar, int start) {
        return cs.toString().indexOf(searchChar.toString(), start);
    }


}
